package de.openkanban.server.service.impl;

import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.dozer.DozerBeanMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import de.openkanban.server.dao.ActivityDao;
import de.openkanban.server.domain.Activity;
import de.openkanban.server.domain.Board;
import de.openkanban.server.domain.Kanban;
import de.openkanban.server.domain.User;
import de.openkanban.server.service.ActivityService;
import de.openkanban.server.service.BoardService;
import de.openkanban.server.service.HistoryService;
import de.openkanban.server.service.KanbanService;
import de.openkanban.shared.dto.KanbanDto;
import de.openkanban.shared.util.CollectionStringTransformer;
import de.openkanban.shared.util.HistoryAction;
import de.openkanban.shared.util.StatusCodes;

/**
 * standard service implementation of {@code ActivityService}
 */
@Service
public class ActivityServiceImpl implements ActivityService {

	@Autowired
	private ActivityDao activityDao;

	@Autowired
	private HistoryService historyService;

	@Autowired
	private KanbanService kanbanService;

	@Autowired
	private BoardService boardService;

	@Autowired
	private DozerBeanMapper mapper;

	@Override
	public Activity getActivity(Long activityId) {
		return activityDao.getActivity(activityId);
	}

	@Override
	public List<Activity> getAll() {
		return activityDao.getAll();
	}

	@Override
	public List<Activity> getAllByBoardId(Long boardId) {
		return activityDao.getAllByBoardId(boardId);
	}

	@Override
	public Activity createActivity(Activity activity) {
		return activityDao.createActivity(activity);
	}

	@Override
	@Transactional
	public StatusCodes removeActivity(Long activityId) {
		Board board = boardService.getBoardByActivityIdLong(activityId);
		Activity activityToRemove = activityDao.getActivity(activityId);
		board.getActivities().remove(activityToRemove);
		return activityDao.removeActivity(activityToRemove);
	}

	@Override
	public Activity updateActivity(Activity activity) {
		return activityDao.updateActivity(activity);
	}

	@Override
	@Transactional
	public KanbanDto addKanbanToActivity(Long userId, Long parentActivityId, KanbanDto kanbanDto) {
		CollectionStringTransformer cst = new CollectionStringTransformer(new HashSet<Long>(), kanbanDto.getKanbanUserString());
		Collection <Long> kanbanUser = cst.toCollection();
		if(kanbanUser != null && kanbanUser.size() > 0 ){
			kanbanDto.setKanbanUserIds((Set<Long>) kanbanUser);
		}
		
		
		Kanban newKanban = kanbanService.createKanban(kanbanDto.getTitle(), kanbanDto.getDescription(), kanbanDto.getEffort(),
				kanbanDto.getKanbanUserIds());
		Kanban kanban = activityDao.addKanbanToActivity(parentActivityId, newKanban);

		if (kanban != null) {
			historyService.writeHistoryEntry(userId, parentActivityId, HistoryAction.CREATE_KANBAN, newKanban.getHistoryText());
		} else {
			return new KanbanDto(StatusCodes.KANBAN_ERROR);
		}

		KanbanDto newKanbanDto =  mapper.map(kanban, KanbanDto.class);
		
		String userIdsString = createUserIdString(kanban);
		newKanbanDto.setKanbanUserString(userIdsString);
		return newKanbanDto;
	}

	private String createUserIdString(Kanban kanban) {
		String userIdsString = null;
		if(kanban.getKanbanUser() != null && kanban.getKanbanUser().size() > 0){
			Set<Long> tmpUserId = new HashSet<Long>();
			for (User user : kanban.getKanbanUser()) {
				tmpUserId.add(user.getUserId());
			}
			CollectionStringTransformer tmpCst = new CollectionStringTransformer(tmpUserId);
			userIdsString = tmpCst.toString();
		}
		return userIdsString;
	}

	@Override
	@Transactional
	public KanbanDto moveKanbanToActivity(Long userId, Long newParentActivityId, Long kanbanId) {
		Activity sourceActivity = activityDao.getActivityByKanbanId(kanbanId);

		if (!isKanbanMoveAllowed(sourceActivity.getActivityId(), newParentActivityId)) {
			return new KanbanDto(StatusCodes.INVALID_KANBAN_TARGET);
		}

		Kanban kanban = activityDao.moveKanbanToActivity(newParentActivityId, kanbanId);
		kanban.setPullable(false);
		
		if (kanban != null) {
			if (isMovedBackward(newParentActivityId, sourceActivity.getActivityId())) {
				historyService.writeHistoryEntry(userId, newParentActivityId, HistoryAction.PULL_KANBAN_BACK,
						kanban.getHistoryText());
			} else if (isDifferentActivty(newParentActivityId, sourceActivity)) {
				historyService.writeHistoryEntry(userId, newParentActivityId, HistoryAction.PULL_KANBAN,
						kanban.getHistoryText());
			}
		}

		return mapper.map(kanban, KanbanDto.class);
	}
	
	@Override
	public Activity getActivityByKanbanId(Long kanbanId) {
		return activityDao.getActivityByKanbanId(kanbanId);
	}

	private boolean isKanbanMoveAllowed(Long sourceActivityId, Long targetActivityId) {
		if (activityIdsBelongToTheSameBoard(sourceActivityId, targetActivityId)) {
			if (isMovedBackward(targetActivityId, sourceActivityId)) {
				return true;
			}

			Long boardId = boardService.getBoardByActivityIdLong(sourceActivityId).getBoardId();
			List<Activity> activityList = activityDao.getActivityRange(boardId, sourceActivityId, targetActivityId);
			if (activityList.size() == 2) {
				return true;
			}
		}
		return false;
	}

	private boolean activityIdsBelongToTheSameBoard(Long... activityIds) {
		if (activityIds != null && activityIds.length != 0) {
			if (activityIds.length == 1) {
				return true;
			}
			Long tempBoardId = null;
			for (Long activityId : activityIds) {
				if (tempBoardId == null) {
					tempBoardId = boardService.getBoardByActivityIdLong(activityId).getBoardId();
				} else {
					Long currBoardId = boardService.getBoardByActivityIdLong(activityId).getBoardId();
					if (tempBoardId != currBoardId) {
						return false;
					}
					tempBoardId = currBoardId;
				}
			}
		}
		return true;
	}

	private boolean isDifferentActivty(Long parentActivityId, Activity sourceActivity) {
		return parentActivityId != sourceActivity.getActivityId();
	}

	private boolean isMovedBackward(Long targetActivityId, Long sourceActivityId) {
		return sourceActivityId > targetActivityId;
	}

}
